/**************************************功能介绍***********************************
Description:	状态机实现按键消抖模板	
Change history:    
*********************************************************************************/
    
//---------<模块及端口声名>------------------------------------------------------
module fsm_key#(parameter WIDTH = 3,TIME_20MS = 1000_000)( 
    input				    clk		,
    input				    rst_n	,
    input       [WIDTH-1:0]	key_in	,
    output  reg [WIDTH-1:0] key_down
);								 
//---------<参数定义>--------------------------------------------------------- 
    //状态机参数        独热码编码
    localparam  IDLE        = 4'b0001,//空闲状态
                FILTER_DOWN = 4'b0010,//按键按下抖动状态
                HOLD        = 4'b0100,//按键稳定按下状态
                FILTER_UP   = 4'b1000;//按键释放抖动状态

//---------<内部信号定义>-----------------------------------------------------
    reg     [3:0]       state_c         ;//现态
    reg     [3:0]       state_n         ;//次态
    reg     [WIDTH-1:0] key_r0          ;//同步打拍
    reg     [WIDTH-1:0] key_r1          ;
    reg     [WIDTH-1:0] key_r2          ;
    wire    [WIDTH-1:0] n_edge          ;//下降沿
    wire    [WIDTH-1:0] p_edge          ;//上升沿   
    reg	    [19:0]	    cnt_20ms	   	;//延时计数器（20ms）
    wire				add_cnt_20ms	;
    wire				end_cnt_20ms	;  

    //状态转移条件信号
    wire                idle2filter_down    ;
    wire                filter_down2idle    ;
    wire                filter_down2hold    ;
    wire                hold2filter_up      ;
    wire                filter_up2hold      ;
    wire                filter_up2idle      ;  

//****************************************************************
//--状态机
//****************************************************************
    //第一段：时序逻辑描述状态转移
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            state_c <= IDLE;
        end 
        else begin 
            state_c <= state_n;
        end 
    end
    
    //第二段：组合逻辑描述状态转移规律和状态转移条件
    always @(*)begin 
        case (state_c)
            IDLE        : begin 
                if(idle2filter_down)begin 
                    state_n = FILTER_DOWN;
                end
                else begin 
                    // state_n = IDLE;
                    state_n = state_c;
                end
            end
            FILTER_DOWN : begin 
                if(filter_down2idle)begin 
                    state_n = IDLE;
                end
                else if(filter_down2hold)begin 
                    state_n = HOLD;
                end
                else begin 
                    state_n = state_c;
                end
            end
            HOLD        : begin 
                if(hold2filter_up)begin 
                    state_n = FILTER_UP;
                end
                else begin 
                    state_n = state_c;
                end
            end
            FILTER_UP   : begin 
                if(filter_up2hold)begin 
                    state_n = HOLD;
                end
                else if(filter_up2idle)begin 
                    state_n = IDLE;
                end
                else begin 
                    state_n = state_c;
                end
            end
            default: state_n = IDLE;
        endcase
    end

    assign idle2filter_down = (state_c == IDLE) && n_edge;
    assign filter_down2idle = (state_c == FILTER_DOWN) && p_edge;
    assign filter_down2hold = (state_c == FILTER_DOWN) && end_cnt_20ms && !p_edge;
    assign hold2filter_up   = (state_c == HOLD) && p_edge;
    assign filter_up2hold   = (state_c == FILTER_UP) && n_edge;
    assign filter_up2idle   = (state_c == FILTER_UP) && end_cnt_20ms;
                
//****************************************************************
//--n_edge、p_edge 
//****************************************************************             
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            key_r0 <= {WIDTH{1'b1}};
            key_r1 <= {WIDTH{1'b1}};
            key_r2 <= {WIDTH{1'b1}};
        end 
        else begin 
            key_r0 <= key_in;
            key_r1 <= key_r0;
            key_r2 <= key_r1; 
        end 
    end
    
    assign n_edge = ~key_r1 & key_r2;//下降沿
    assign p_edge = ~key_r2 & key_r1;//上升沿

//****************************************************************
//--cnt_20ms
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_20ms <= 'd0;
        end 
        else if(add_cnt_20ms)begin 
            if(end_cnt_20ms || filter_down2idle || filter_up2hold)begin 
                cnt_20ms <= 'd0;
            end
            else begin 
                cnt_20ms <= cnt_20ms + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_20ms = (state_c == FILTER_DOWN) || (state_c == FILTER_UP);
    assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;
    
//****************************************************************
//--key_down
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            key_down <= 'd0;
        end 
        else begin 
            key_down <= filter_down2hold ? ~key_r2 : 1'b0;
        end
    end

endmodule